<!-- KeepAlive.vue -->

<template>
  <component :is="instance.ctx.render" />
</template>

<script setup lang="ts">

import {
  h,
  ref,
  onMounted,
  onUnmounted,
  onActivated,
  onDeactivated,
  type VNode,
  type Component,
  type VNodeNormalizedChildren,
  getCurrentInstance,
  withCtx,
  useSlots,
  defineProps,
  watch,
  shallowRef
} from 'vue'

interface KeepAliveProps {
  include?: string | RegExp | (string | RegExp)[]
  exclude?: string | RegExp | (string | RegExp)[]
  max?: number | string
}

const props = withDefaults(defineProps<KeepAliveProps>(), {
  include: undefined,
  exclude: undefined,
  max: undefined
})

type CacheKey = string | symbol
type CacheEntry = {
  vnode: VNode
  // 添加时间戳用于LRU计算
  timestamp: number
}

const cache = new Map<CacheKey, CacheEntry>()
const keys = shallowRef<CacheKey[]>([])
const pendingCacheKey = ref<CacheKey | null>(null)

// 获取组件名称
const getComponentName = (component: Component): string | undefined => {
  return (component as any).name || (component as any).displayName
}

// 匹配规则
const matches = (pattern: string | RegExp | (string | RegExp)[], name: string): boolean => {
  if (Array.isArray(pattern)) {
    return pattern.some(p => matches(p, name))
  } else if (typeof pattern === 'string') {
    return name === pattern
  } else if (pattern instanceof RegExp) {
    return pattern.test(name)
  }
  return false
}

// 判断是否需要缓存
const shouldCache = (vnode: VNode): boolean => {
  const name = getComponentName(vnode.type as Component)
  return !!name && (
    (props.include ? matches(props.include, name) : true) &&
    (props.exclude ? !matches(props.exclude, name) : true)
  )
}

// LRU缓存清理
const pruneCache = (filter?: (name: string) => boolean) => {
  cache.forEach((entry, key) => {
    const name = getComponentName(entry.vnode.type as Component) || ''
    if (name && filter && !filter(name)) {
      pruneCacheEntry(key)
    }
  })
}

// 移除单个缓存项
const pruneCacheEntry = (key: CacheKey) => {
  const entry = cache.get(key)
  if (entry) {
    // 触发组件卸载生命周期
    entry.vnode.component?.unmount()
    cache.delete(key)
    keys.value = keys.value.filter(k => k !== key)
  }
}

// 更新缓存过滤器
watch(() => [props.include, props.exclude], ([include, exclude]) => {
  pruneCache((name) => {
    if (include && !matches(include, name)) return false
    if (exclude && matches(exclude, name)) return false
    return true
  })
})

// 生成缓存key
const getCacheKey = (vnode: VNode): CacheKey => {
  return vnode.key == null
    ? getComponentName(vnode.type as Component) || Symbol('cache-key')
    : vnode.key
}

// 渲染函数
const render = (): VNodeNormalizedChildren => {
  const slots = useSlots()
  const rawVNode = slots.default?.()[0] as VNode

  if (!rawVNode || !shouldCache(rawVNode)) {
    return rawVNode
  }

  const key = getCacheKey(rawVNode)
  const cachedEntry = cache.get(key)
  pendingCacheKey.value = key

  if (cachedEntry) {
    // 更新访问时间戳
    cachedEntry.timestamp = Date.now()
    // 复用组件实例
    rawVNode.component = cachedEntry.vnode.component

    // 添加keep-alive标记
    if (rawVNode.shapeFlag & 256 /* ShapeFlags.COMPONENT_KEPT_ALIVE */) {
      rawVNode.shapeFlag |= 512 /* ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE */
    }
  } else {
    // 创建新缓存项
    cache.set(key, { vnode: rawVNode, timestamp: Date.now() })
    keys.value.push(key)

    // 处理最大缓存数
    if (props.max && keys.value.length > parseInt(props.max as string, 10)) {
      // 找到最久未使用的key
      const oldestKey = keys.value.reduce((a, b) =>
        (cache.get(a)!.timestamp < cache.get(b)!.timestamp) ? a : b
      )
      pruneCacheEntry(oldestKey)
    }
  }

  // 标记为保持激活状态
  rawVNode.shapeFlag |= 256 /* ShapeFlags.COMPONENT_KEPT_ALIVE */
  return rawVNode
}

// 生命周期处理
const onMountedOrActivated = (vnode: VNode) => {
  const instance = getCurrentInstance()
  if (!instance) return

  const key = pendingCacheKey.value
  if (key && cache.has(key)) {
    onActivated(() => {
      instance.emit('activated', vnode)
    })
  } else {
    onMounted(() => {
      instance.emit('mounted', vnode)
    })
  }

  onDeactivated(() => {
    instance.emit('deactivated', vnode)
  })
}

// 创建带上下文渲染函数
const instance = getCurrentInstance()!
instance.ctx.render = withCtx(() => {
  const vnode = render() as VNode
  if (vnode && vnode.component) {
    onMountedOrActivated(vnode)
  }
  return vnode
})
</script>
